package com.example.demo.config.one;

import com.example.demo.service.impl.ServiceImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author juntao.zhang
 * @version V1.0
 * @Description: TODO
 * @Package com.example.demo.config.Myorg.springframework.web.method.support
 * @date 2018/4/12 22:34
 */
public class MyHandlerMethodArgumentResolverComposite extends HandlerMethodArgumentResolverComposite {

    protected final Log logger = LogFactory.getLog(getClass());

    private final List<HandlerMethodArgumentResolver> argumentResolvers =
            new LinkedList<HandlerMethodArgumentResolver>();

    private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
            new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);

    /**
     * Add the given {@link HandlerMethodArgumentResolver}.
     */
    public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) {
        this.argumentResolvers.add(resolver);
        return this;
    }

    /**
     * Add the given {@link HandlerMethodArgumentResolver}s.
     * @since 4.3
     */
    public HandlerMethodArgumentResolverComposite addResolvers(HandlerMethodArgumentResolver... resolvers) {
        if (resolvers != null) {
            for (HandlerMethodArgumentResolver resolver : resolvers) {
                this.argumentResolvers.add(resolver);
            }
        }
        return this;
    }

    /**
     * Add the given {@link HandlerMethodArgumentResolver}s.
     */
    public HandlerMethodArgumentResolverComposite addResolvers(List<? extends HandlerMethodArgumentResolver> resolvers) {
        if (resolvers != null) {
            for (HandlerMethodArgumentResolver resolver : resolvers) {
                this.argumentResolvers.add(resolver);
            }
        }
        return this;
    }

    /**
     * Return a read-only list with the contained resolvers, or an empty list.
     */
    public List<HandlerMethodArgumentResolver> getResolvers() {
        return Collections.unmodifiableList(this.argumentResolvers);
    }

    /**
     * Clear the list of configured resolvers.
     * @since 4.3
     */
    public void clear() {
        this.argumentResolvers.clear();
    }


    /**
     * Whether the given {@linkplain MethodParameter method parameter} is supported by any registered
     * {@link HandlerMethodArgumentResolver}.
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return (getArgumentResolver(parameter) != null);
    }

    /**
     * Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
     * @throws IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HandlerMethodArgumentResolver resolver = getFeignInterfaceArgumentResolver(parameter);
        if (Objects.isNull(resolver))
            resolver= getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
        }
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }

    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
                            parameter.getGenericParameterType() + "]");
                }
                if (methodArgumentResolver.supportsParameter(parameter)) {
                    result = methodArgumentResolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

    private HandlerMethodArgumentResolver getFeignInterfaceArgumentResolver(MethodParameter parameter) {
        try {
            Class<?>[] interfaces = ServiceImpl.class.getInterfaces();
            if (Objects.nonNull(interfaces)){
                Optional<Method> mathodOptional = Arrays.stream(interfaces).map(inter -> {
                    try {
                        return inter.getDeclaredMethod(parameter.getMethod().getName(), parameter.getMethod().getParameterTypes());
                    } catch (NoSuchMethodException e) {
                    }
                    return null;
                }).filter(Objects::nonNull).findAny();
                if (mathodOptional.isPresent()){
                    MethodParameter methodParameter = SynthesizingMethodParameter.forMethodOrConstructor(mathodOptional.get(), parameter.getParameterIndex());
                    return getArgumentResolver(methodParameter);
                }
            }
        }catch (Exception e){
            logger.error("FeignInterfaceArgumentResolver",e);
        }
        return null;
    }
}
